﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Crm.Sdk.Messages;
using System.Threading;

namespace OptionSetBatchManager
{
    public static class ActionRequest
    {
        private static volatile bool s_CancelRequest = false;

        /// <summary>
        /// Create IEnumerable sequence of OrganizationResponse from request RetrieveAllEntitiesRequest and RetrieveAllOptionSetsRequest
        /// </summary>
        /// <param name="service"></param>
        /// <param name="includeGlobalOptionSet">Flag indicates wheather to include global OptionSets</param>
        /// <returns></returns>
        public static IEnumerable<OrganizationResponse> CreateRetrieveAllOptionSetRequest(IOrganizationService service, bool includeGlobalOptionSet)
        {
            RetrieveAllEntitiesRequest reqAllEntities = new RetrieveAllEntitiesRequest
            {
                EntityFilters = EntityFilters.Entity | EntityFilters.Attributes,
                RetrieveAsIfPublished = true
            };
            yield return service.Execute(reqAllEntities);

            if (includeGlobalOptionSet)
            {
                RetrieveAllOptionSetsRequest globalOpReq = new RetrieveAllOptionSetsRequest { RetrieveAsIfPublished = true };
                yield return service.Execute(globalOpReq);
            }
        }
                
        /// <summary>
        /// Reset value of each OptionSet's item value with specified initial value
        /// </summary>
        /// <param name="service"></param>
        /// <param name="optionSetData">OptionSet data to reset value</param>
        /// <param name="initValue">The initial number to begin to increment</param>
        /// <returns>Create IEnumerable sequence of each operation result in string format</returns>
        public static IEnumerable<string> CreateResetValueRequest(IOrganizationService service, OptionSetData optionSetData, int initValue)
        {
            // We have to delete all existing OptionSet's items and re-create them with the new value since SDK doesn't support to modify the value number (though we could do via CRM web)

            foreach (var optionMetadata in optionSetData.OptionSetMetadata.Options)
            {
                // Use the DeleteOptionValueRequest message 
                // to remove each option set item.
                DeleteOptionValueRequest deleteOptionValueRequest = optionSetData.IsGlobal ?
                    new DeleteOptionValueRequest
                    {
                        OptionSetName = optionSetData.GlobalOptionSetName,
                        Value = optionMetadata.Value.Value
                    } :
                    new DeleteOptionValueRequest
                    {
                        EntityLogicalName = optionSetData.EntityName,
                        AttributeLogicalName = optionSetData.AttributeName,
                        Value = optionMetadata.Value.Value
                    };

                // Execute the delete request.
                service.Execute(deleteOptionValueRequest);
                yield return string.Format("Deleted Option Set Item - Value:{0}, Label:{1} from attribute {2}", optionMetadata.Value.Value, optionMetadata.Label.UserLocalizedLabel.Label, optionSetData);
            }

            foreach (var optionMetadata in optionSetData.OptionSetMetadata.Options)
            {
                // Use InsertOptionValueRequest to insert a new option as a replacement of the deleted above
                InsertOptionValueRequest insertOptionValueRequest = optionSetData.IsGlobal ?
                    new InsertOptionValueRequest
                    {
                        OptionSetName = optionSetData.GlobalOptionSetName,
                        Label = optionMetadata.Label,
                        Value = initValue++
                    } :
                    new InsertOptionValueRequest
                    {
                        EntityLogicalName = optionSetData.EntityName,
                        AttributeLogicalName = optionSetData.AttributeName,
                        Label = optionMetadata.Label,
                        Value = initValue++
                    };

                // Execute the request and store the newly inserted option value 
                // for cleanup, used in the later part of this sample.
                service.Execute(insertOptionValueRequest);
                yield return string.Format("Add new Option Set Item - Value:{0}, Label:{1} from attribute {2}", optionMetadata.Value.Value, optionMetadata.Label.UserLocalizedLabel.Label, optionSetData);
            }

            // If it is a global OptionSet then also publish it
            if (optionSetData.IsGlobal)
            {
                yield return PublishGlobalOptionSet(service, optionSetData);
            }
            else
            {
                yield return PublishAllCustomization(service);
            }
        }

        /// <summary>
        /// Reset value of each OptionSet's item value with specified initial value for multiple OptionSets
        /// </summary>
        /// <param name="service"></param>
        /// <param name="optionSetDatas">All OptionSets data to reset value</param>
        /// <param name="initValue">The initial number to begin to increment</param>
        /// <returns>Create IEnumerable sequence of each operation result in string format</returns>
        public static IEnumerable<string> CreateResetValueRequest(IOrganizationService service, List<OptionSetData> optionSetDatas, int initValue)
        {
            // Reset cancel flag
            s_CancelRequest = false;

            // We have to delete all existing OptionSet's items and re-create them with the new value since SDK doesn't support to modify the value number (though we could do via CRM web)
            var value = initValue;
            foreach (var optionSetData in optionSetDatas)
            {
                // Stop operation if the user cancels operation
                if (s_CancelRequest)
                {
                    yield return "User cancelled order operation.";                    
                    break;
                }

                yield return string.Format("Resetting Option Set: {0}", optionSetData);

                foreach (var optionMetadata in optionSetData.OptionSetMetadata.Options)
                {
                    // Use the DeleteOptionValueRequest message 
                    // to remove each option set item.
                    DeleteOptionValueRequest deleteOptionValueRequest = optionSetData.IsGlobal ?
                        new DeleteOptionValueRequest
                        {
                            OptionSetName = optionSetData.GlobalOptionSetName,
                            Value = optionMetadata.Value.Value,
                        } :
                        new DeleteOptionValueRequest
                        {
                            EntityLogicalName = optionSetData.EntityName,
                            AttributeLogicalName = optionSetData.AttributeName,
                            Value = optionMetadata.Value.Value
                        };

                    // Execute the delete request.
                    service.Execute(deleteOptionValueRequest);
                    yield return string.Format("Deleted Option Set Item - Value:{0}, Label:{1} from attribute {2}", optionMetadata.Value.Value, optionMetadata.Label.UserLocalizedLabel.Label, optionSetData);
                }
                foreach (var optionMetadata in optionSetData.OptionSetMetadata.Options)
                {
                    // Use InsertOptionValueRequest to insert a new option as a replacement of the deleted above
                    InsertOptionValueRequest insertOptionValueRequest = optionSetData.IsGlobal ?
                        new InsertOptionValueRequest
                        {
                            OptionSetName = optionSetData.GlobalOptionSetName,
                            Label = optionMetadata.Label,
                            Value = value++
                        } :
                        new InsertOptionValueRequest
                        {
                            EntityLogicalName = optionSetData.EntityName,
                            AttributeLogicalName = optionSetData.AttributeName,
                            Label = optionMetadata.Label,
                            Value = value++
                        };

                    // Execute the request and store the newly inserted option value 
                    // for cleanup, used in the later part of this sample.
                    service.Execute(insertOptionValueRequest);
                    yield return string.Format("Add new Option Set Item - Value:{0}, Label:{1} from attribute {2}", optionMetadata.Value.Value, optionMetadata.Label.UserLocalizedLabel.Label, optionSetData);
                }

                // If it is a global OptionSet then also publish it
                if (optionSetData.IsGlobal)
                {
                    yield return PublishGlobalOptionSet(service, optionSetData);
                }
                else
                {
                    yield return PublishAllCustomization(service);
                }
                // Reset value back to initValue for the next OptionSet
                value = initValue;
            }
        }

        /// <summary>
        /// Order OptionSet's item for multiple OptionSets
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="service"></param>
        /// <param name="optionSetDatas">All OptionSets to order</param>
        /// <param name="sortCondition">Specficy sort condition</param>
        /// <returns>Create IEnumerable sequence of each operation result in string format</returns>
        public static IEnumerable<string> CreateOrderRequest<T>(IOrganizationService service, List<OptionSetData> optionSetDatas, Func<OptionMetadata, T> sortCondition)
        {
            // Reset cancel flag
            s_CancelRequest = false;

            foreach (var optionSetData in optionSetDatas)
            {
                // Stop operation if the user cancels operation
                if (s_CancelRequest)
                {
                    yield return "User cancelled order operation.";
                    break;
                }

                OrderOptionRequest orderReq = new OrderOptionRequest();
                if (optionSetData.IsGlobal)
                {
                    orderReq.OptionSetName = optionSetData.GlobalOptionSetName;
                }
                else
                {
                    orderReq.EntityLogicalName = optionSetData.EntityName;
                    orderReq.AttributeLogicalName = optionSetData.AttributeName;
                }
                orderReq.Values = optionSetData.OptionSetMetadata.Options.Where(c => c.Value.HasValue).OrderBy(c => sortCondition(c)).Select(c => c.Value.Value).ToArray();

                // Execute the request.
                service.Execute(orderReq);

                // If it is a global OptionSet then also publish it
                if (optionSetData.IsGlobal)
                {
                    yield return PublishGlobalOptionSet(service, optionSetData);
                }
                else
                {
                    yield return PublishAllCustomization(service);
                }

                yield return string.Format("Reorder {0} completed successfully", optionSetData);
            }
        }

        /// <summary>
        /// Publish the Global OptionSet
        /// </summary>
        /// <param name="service"></param>
        /// <param name="optionSetData"></param>
        /// <returns></returns>
        private static string PublishGlobalOptionSet(IOrganizationService service, OptionSetData optionSetData)
        {
            PublishXmlRequest pxReq3 = new PublishXmlRequest { ParameterXml = String.Format("<importexportxml><optionsets><optionset>{0}</optionset></optionsets></importexportxml>", optionSetData.GlobalOptionSetName) };
            service.Execute(pxReq3);
            return string.Format("Global Option Set {0} is published", optionSetData);
        }

        /// <summary>
        /// Publish all customizations
        /// </summary>
        /// <param name="service"></param>
        /// <returns></returns>
        private static string PublishAllCustomization(IOrganizationService service)
        {
            // Customizations must be published after an entity is updated.
            PublishAllXmlRequest publishRequest = new PublishAllXmlRequest();
            service.Execute(publishRequest);
            return "Customizations were published.";
        }

        public static void CancelOperation()
        {
            s_CancelRequest = true;
        }
    }
}


